// Reference:
// -http://golang.org/src/pkg/go/ast/ast.go
// -http://golang.org/src/pkg/go/ast/walk.go 

package main

import (
	"go/ast"
	"go/parser"
	"go/token"
	"fmt"
	"flag"
	"os"
	"reflect"
	"strings"
)

// Avoid typing "fmt.Printf("...\n", ...) every time
func printf(format string, v ...interface{}) (n int, errno os.Error) {
	return fmt.Printf(format + "\n", v...)
}

// Visitor for ast.Walk
type printVisitor struct {
	weight float
        priorWeight float
}

func (f *printVisitor) Visit(node ast.Node) ast.Visitor {

	if node == nil {
		return f
	}

	typ := reflect.NewValue(node).Type().String()
	// Get rid of redundant package prefix
	typ = strings.Split(typ, ".", -1)[1]

	w := 1.0
	// If statement then add weight
	if strings.Contains(typ, "Stmt") {
		w = f.weight
	}
	if strings.Contains(typ, "CaseClause") || strings.Contains(typ, "CommClause") {
		w = f.priorWeight
	}
	printf("%s: %f", typ, w)
	printf("%s.Pos: %d", typ, node.Pos()) // All nodes have Pos accessor method

	// 3 classes of nodes: Expression & type nodes,
	// statement nodes, and declaration nodes
	switch n := node.(type) {

	// Comments
	case *ast.Comment:
		printf("%s.Text.Length: %d", typ, len(n.Text))
	case *ast.CommentGroup:
		printf("%s.List.Length: %d", typ, len(n.List))

	// Expressions
	case *ast.Field:
		printf("%s.Names.Length: %d", typ, len(n.Names))
	case *ast.FieldList:
		printf("%s.List.Length: %d", typ, len(n.List))
		printf("%s.Closing: %d", typ, n.Closing)
		printf("%s.NumFields: %d", typ, n.NumFields())
	case *ast.Ident:
		printf("%s.Name.Length: %d", typ, len(n.Name))
	case *ast.CompositeLit:
		printf("%s.Lbrace: %d", typ, n.Lbrace)
		printf("%s.Elts.Length: %d", typ, len(n.Elts))
		printf("%s.Rbrace: %d", typ, n.Rbrace)
	case *ast.ParenExpr:
		printf("%s.Lparen: %d", typ, n.Lparen)
		printf("%s.Rparen: %d", typ, n.Rparen)
	case *ast.CallExpr:
		printf("%s.Lparen: %d", typ, n.Lparen)
		printf("%s.Args.Length: %d", typ, len(n.Args))
		printf("%s.Ellipsis: %d", typ, n.Ellipsis)
		printf("%s.Rparen: %d", typ, n.Rparen)
	case *ast.KeyValueExpr:
		printf("%s.Colon: %d", typ, n.Colon)
	case *ast.BinaryExpr:
		fmt.Println("Binary expression")

	// Types
	case *ast.StructType:
		printf("%s.Fields.Length: %d", typ, len(n.Fields.List))
	case *ast.FuncType:
		printf("%s.Params.Length: %d", typ, n.Params.NumFields())
		printf("%s.Results.Length: %d", typ, n.Results.NumFields())
	case *ast.InterfaceType:
		printf("%s.Methods.Length: %d", typ, len(n.Methods.List))

	// Statements
	case *ast.AssignStmt:
		printf("%s.Lhs.Length: %d", typ, len(n.Lhs))
		// Usage of "=" vs. ":="
		if n.Tok == token.DEFINE {
			printf("%s.Tok.DEFINE: 1", typ)
			return f
		}
		printf("%s.Tok.ASSIGN: 1", typ)
	case *ast.ReturnStmt:
		printf("%s.Results.Length: %d", typ, len(n.Results))
	case *ast.BlockStmt:
		printf("%s.Lbrace: %d", typ, n.Lbrace)
		printf("%s.List.Length: %d", typ, len(n.List))
		printf("%s.Rbrace: %d", typ, n.Rbrace)
	case *ast.CaseClause:
		printf("%s.Colon: %d", typ, n.Colon)
		printf("%s.Body.Length: %d", typ, len(n.Body))
	case *ast.TypeCaseClause:
		printf("%s.Types.Length: %d", typ, len(n.Types))
		printf("%s.Colon: %d", typ, n.Colon)
		printf("%s.Body.Length: %d", typ, len(n.Body))
	case *ast.CommClause:
		printf("%s.Colon: %d", typ, n.Colon)
		printf("%s.Body.Length: %d", typ, len(n.Body))
	// Handle weighting
	case *ast.IfStmt:
		var t printVisitor
		t.weight = f.weight/2
		t.priorWeight = f.weight
		return &t
	case *ast.SwitchStmt:
	case *ast.SelectStmt:
	case *ast.TypeSwitchStmt:
		numCases := len(n.Body.List)
		var t printVisitor
		t.weight = f.weight/float(numCases)
		t.priorWeight = f.weight
		return &t

	// Declarations
	case *ast.ValueSpec:
		printf("%s.Names.Length: %d", typ, len(n.Names))
	case *ast.GenDecl:
		printf("%s.Lparen: %d", typ, n.Lparen)
		printf("%s.Rparen: %d", typ, n.Rparen)
	case *ast.FuncDecl:
		if n.Recv != nil {
			printf("%s.Recv", typ)
		}
		printf("%s.Name.Length: %d", typ, len(n.Name.Name))
		printf("%s.Body.List.Length: %d", typ, len(n.Body.List))

	// Files & packages?

	default:
		return f

	} // End cases

	return f
}


// File size in kB of a file
func size(name string) int64 {
	file, _ := os.Open(name, os.O_RDONLY, 0)
	defer file.Close()
	var buf [100]byte
	len := 0
	for {
		n, e := file.Read(buf[0:])
		len += n
		if e == os.EOF {
			break
		}
	}
	return int64(len)
}


func main() {
	flag.Parse()
	filename := flag.Arg(0)
	file, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0)
	if err != nil {
		fmt.Println(filename + "\t" + err.String())
		return
	}
	fmt.Printf("FileSizeKB: %d\n", size(filename)/1000)
	// Instance
	var f printVisitor
	f.weight = 1
        f.priorWeight = 1
	ast.Walk(&f, file)
}
